/*
 * @(#)DataflowControllerImpl.java   09/10/11
 * 
 * Copyright (C) 2009 Ecole des Mines d'Ales
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Contact: yulinz88@gmail.com
 */



package fr.ema.arch3d.ref.interceptors.impls;

import fr.ema.arch3d.ref.controllers.interfaces.ConnectorController;
import fr.ema.arch3d.ref.interceptors.interfaces.DataflowController;

import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.NoSuchInterfaceException;
import org.objectweb.fractal.julia.loader.Initializable;
import org.objectweb.fractal.julia.loader.Tree;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import java.util.Map;

//~--- classes ----------------------------------------------------------------

/**
 * Class description
 *
 *
 * @version        1.0, 09/09/13
 * @author         Huaxi (Yulin) ZHANG
 */
public class DataflowControllerImpl implements DataflowController, Initializable {

    /** Field description */
    private boolean stopByEMOff = false,
                    stopByEMOn  = false;

    /** Field description */
    private Object[] args;

    /** Field description */
    private Object[] argsNew;

    /** Field description */
    private ConnectorController cc;

    /** Field description */
    private int defineTestTimes;

    /** Field description */
    private int ii;

    /** Field description */
    private String impl;

    /** Field description */
    private int jj;

    /** Field description */
    private Method m;

    /** Field description */
    private Object newR;

    // private Object service;

    /** Field description */
    private Object oldR;

    /** Field description */
    private boolean record, passive;

    /** Field description */
    private Object[][] result;

    /** Field description */
    private Component[] stateTarget;

    /** Field description */
    private Component target;

    /** Field description */
    private int testKey;

    /** Field description */
    private int testTimes;

    /**
     * Method description
     *
     * @param args  
     */
    public void initialize(final Tree args) {
        String s = args.getSubTree(0).toString();
    }

    /**
     * Method description
     *
     * @param target  
     * @param m  
     * @param args  
     * @return  
     */
    public Object controlDataflow(Object target, Method m, Object[] args) {
        try {
            this.target = (Component) target;

            Field  ss[] = target.getClass().getDeclaredFields();
            Field  s    = target.getClass().getDeclaredField("service");
            Field  d    = target.getClass().getDeclaredField("bindings");
            Object service;
            Object oldR, newR;

            this.target = (Component) target;

            ConnectorController cc = (ConnectorController) this.target.getFcInterface("connector-controller");

            this.cc = cc;
            this.m  = m;

            // no evolution
            if (testKey == 0) {
                service = ((Map) d.get(target)).get("r-I");
                s.set(target, service);
                oldR = m.invoke(target, args);

                return oldR;
            }

            // Provided component evolution offline
            if (testKey == 1) {
                if (jj == 0) {
                    service = ((Map) d.get(target)).get("r-I");
                    s.set(target, service);
                    oldR      = m.invoke(target, args);
                    this.oldR = oldR;
                    jj        = 1;
                }

                if (jj == 1) {
                    try {
                        service = ((Map) d.get(target)).get("r-II");
                        s.set(target, service);
                        ii        = 2;
                        newR      = m.invoke(target, args);
                        this.newR = newR;
                    } catch (Exception e) {
                        System.err.println("1new component cannot work well during offline test!!");
                    }

                    jj = 0;
                }

                // System.out.println("here is enter record");
                if (!this.stopByEMOff) {
                    if (record) {
                        this.args = args;
                        recordDataP();
                    }

                    testTimes++;

                    if (!passive) {
                        if (testTimes == defineTestTimes) {
                            finishOfflineTestP();
                        }
                    }
                }

                return this.oldR;
            }

            // Provided component evolution online
            else if (testKey == 2) {
                if (jj == 0) {
                    try {
                        service = ((Map) d.get(target)).get("r-II");
                        s.set(target, service);
                        newR      = m.invoke(target, args);
                        this.newR = newR;
                        jj        = 1;
                    } catch (Exception e) {
                        System.err.println("new verison cannot work correctly during online test!!");
                        testKey = 0;
                        jj      = 0;
                    }
                }

                if (jj == 1) {
                    service = ((Map) d.get(target)).get("r-I");
                    s.set(target, service);
                    ii        = 2;
                    oldR      = m.invoke(target, args);
                    this.oldR = oldR;
                    jj        = 0;
                }

                if (!this.stopByEMOn) {
                    if (record) {
                        this.args = args;
                        recordDataP();
                    }

                    testTimes++;

                    if (!passive) {
                        if (testTimes == defineTestTimes) {
                            finishOnlineTestP();
                        }
                    }
                }

                return this.newR;
            }

            // Required component evolution offline
            else if (testKey == 3) {
                StackTraceElement tab[] = Thread.currentThread().getStackTrace();

                // for(int i=0;i<tab.length;i++)
                // System.out.println("ok"+tab[i].getClassName());
                boolean enter = false;

                for (int i = 0; i < tab.length; i++) {

                    // System.out.println(tab[i]);
                    if ((tab[i].getClassName()).equals(impl)) {
                        enter = true;
                    }
                }

                if (enter) {
                    this.argsNew = args;

                    if (!this.stopByEMOff) {
                        if (record) {
                            recordDataR(false);
                        }

                        testTimes++;
                        ii = 0;

                        if (!passive) {
                            if (testTimes == defineTestTimes * 2) {
                                finishOfflineTestR();
                            }
                        }
                    }

                    // Thread.currentThread().stop();
                    return this.oldR;
                } else {
                    service = ((Map) d.get(target)).get("r-I");
                    s.set(target, service);
                    oldR = m.invoke(target, args);

                    if (!this.stopByEMOff) {
                        this.oldR = oldR;

                        if (record) {
                            this.args = args;
                            recordDataR(true);
                        }

                        testTimes++;

                        if (!passive) {
                            if (testTimes == defineTestTimes * 2) {
                                finishOfflineTestR();
                            }
                        }
                    }

                    return this.oldR;
                }
            }

            // Required component evolution online
            else if (testKey == 4) {
                StackTraceElement tab[] = Thread.currentThread().getStackTrace();
                boolean           enter = false;

                for (int i = 0; i < tab.length; i++) {
                    if ((tab[i].getClassName()).equals(impl)) {
                        enter = true;
                    }
                }

                if (enter) {

                    // System.out.println((String)args[0]);
                    if (!this.stopByEMOn) {
                        if (record) {
                            this.argsNew = args;
                            recordDataR(false);
                        }

                        testTimes++;
                        ii = 0;

                        if (!passive) {
                            if (testTimes == defineTestTimes * 2) {
                                finishOnlineTestR();
                            }
                        }
                    }

                    // Thread.currentThread().stop();
                    return this.oldR;
                } else {

                    // System.out.println((String)args[0]);
                    service = ((Map) d.get(target)).get("r-I");
                    s.set(target, service);
                    oldR = m.invoke(target, args);

                    if (!this.stopByEMOn) {
                        this.oldR = oldR;

                        if (record) {
                            this.args = args;
                            recordDataR(true);
                        }

                        testTimes++;

                        if (!passive) {
                            if (testTimes == defineTestTimes * 2) {
                                finishOnlineTestR();
                            }
                        }
                    }

                    return this.oldR;
                }
            } else {
                return null;
            }
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Method description
     *
     * @return  
     */
    public boolean controlTest() {

//      System.out.println("enter control");
        ii++;

        if (ii == 1) {
            return false;
        } else {
            ii = 0;

            return true;
        }
    }

    /**
     * Method description
     *
     */
    public void recordDataP() {
        if (testTimes < defineTestTimes) {
            result[testTimes][0] = this.m;
            result[testTimes][1] = this.oldR;
            result[testTimes][2] = this.newR;
            result[testTimes][3] = this.args;
        }
    }

    /**
     * Method description
     *
     * @param old  
     */
    public void recordDataR(boolean old) {
        if (testTimes < (defineTestTimes * 2 - 1)) {
            if (old) {
                result[testTimes / 2][0] = this.m;
                result[testTimes / 2][3] = this.oldR;
                result[testTimes / 2][1] = this.args;

                // System.out.println("ok1"+output[testTimes/2]+inputOld[testTimes/2]);
            } else {
                result[testTimes / 2][2] = this.argsNew;

                // output[testTimes/2]=this.oldR;
            }
        }

        if (testTimes == (defineTestTimes * 2 - 1)) {
            if (old) {
                result[testTimes / 2][0] = this.m;
                result[testTimes / 2][3] = this.oldR;
                result[testTimes / 2][1] = this.args;

                // System.out.println("ok11"+output[testTimes/2]+inputOld[testTimes/2]);
            } else {
                result[testTimes / 2][2] = this.argsNew;

                // output[testTimes/2]=this.oldR;
                // System.out.println("ok22"+output[testTimes/2]+inputOld[testTimes/2]);
            }

            // System.out.println("ok33outputold="+output[0]+" new="+inputNew[0]+" old="+inputOld[0]);
        }
    }

/*
    * begin offline test of provided component
    * @see org.objectweb.fractal.connector.interceptors.interfaces.OutputController#beginOfflineTestP(int)
 */

    /**
     * Method description
     *
     * @param defineTestTimes  
     * @param record  
     * @param passive  
     */
    public void beginOfflineTestPDC(int defineTestTimes, boolean record, boolean passive) {
        this.stopByEMOff = false;
        this.stopByEMOn  = false;

        if (passive) {
            result = new Object[10][4];
        } else {
            result = new Object[defineTestTimes][4];
        }

        this.defineTestTimes = defineTestTimes;
        testKey              = 1;
        this.record          = record;
        this.passive         = passive;
    }

/*
    * begin online test of provided component
    * @see org.objectweb.fractal.connector.interceptors.interfaces.OutputController#beginOnlineTestP(int)
 */

    /**
     * Method description
     *
     * @param defineTestTimes  
     */
    public void beginOnlineTestPDC(int defineTestTimes) {
        if (passive) {
            result = new Object[10][4];
        } else {
            result = new Object[defineTestTimes][4];
        }

        this.defineTestTimes = defineTestTimes;
        testKey              = 2;
    }

    /**
     * Method description
     *
     * @param newImplName  
     * @param defineTestTimes  
     * @param record  
     */
    public void beginOfflineTestR(String newImplName, int defineTestTimes, boolean record) {
        this.stopByEMOff = false;
        this.stopByEMOn  = false;

        if (passive) {
            result = new Object[10][4];
        } else {
            result = new Object[defineTestTimes][4];
        }

        this.defineTestTimes = defineTestTimes;
        this.impl            = newImplName;
        testKey              = 3;
        this.record          = record;
    }

    /**
     * Method description
     *
     * @param oldImplName  
     * @param defineTestTimes  
     */
    public void beginOnlineTestR(String oldImplName, int defineTestTimes) {
        if (passive) {
            result = new Object[10][4];
        } else {
            result = new Object[defineTestTimes][4];
        }

        this.defineTestTimes = defineTestTimes;
        this.impl            = oldImplName;
        testKey              = 4;
    }

    /**
     * Method description
     *
     * @throws NoSuchInterfaceException  
     */
    public void finishOfflineTestP() throws NoSuchInterfaceException {
        testKey   = 0;
        testTimes = 0;
        cc.finishOfflineTestP(result);
    }

    /**
     * Method description
     *
     * @throws NoSuchInterfaceException  
     */
    public void finishOnlineTestP() throws NoSuchInterfaceException {
        testKey   = 0;
        testTimes = 0;
        cc.finishOnlineTestP(result);
    }

    /**
     * Method description
     *
     * @throws NoSuchInterfaceException  
     */
    public void finishOfflineTestR() throws NoSuchInterfaceException {
        testKey   = 0;
        testTimes = 0;
        cc.finishOfflineTestR(result);
        ii = 0;
    }

    /**
     * Method description
     *
     * @throws NoSuchInterfaceException  
     */
    public void finishOnlineTestR() throws NoSuchInterfaceException {
        testKey   = 0;
        testTimes = 0;
        cc.finishOnlineTestR(result);
        ii = 0;
    }

    /**
     * Method description
     *
     * @return  
     */
    public Object[][] finishTestOCOff() {
        testKey          = 0;
        testTimes        = 0;
        ii               = 0;
        this.stopByEMOff = true;

        return result;
    }

    /**
     * Method description
     *
     * @return  
     */
    public Object[][] finishTestOCOn() {
        testKey         = 0;
        testTimes       = 0;
        ii              = 0;
        this.stopByEMOn = true;

        return result;
    }
}
